﻿#include "pch.h"
#include "sbcs.h"
#include "extend/sbmem.h"
#include "kstub_mutex.h"
#ifdef _MSC_VER
#include "zlib/zlib.h"
#else
#include <zlib.h>
#endif

//////////////////////////////////////////////////////////////////////////
// 메모리

extern void _k_heap_init(void);
extern void _k_heap_disp(void);
extern void _k_memproof_init(void);
extern void _k_memproof_disp(void);

extern kcham k_heapvalid(kpointer ptr);
extern kMemBlock* k_mpfalloc(ksize_t size);
extern kMemBlock* k_mpfallocf(ksize_t size);
extern void k_mpffree(kMemBlock* ptr);

//
void _k_mem_init(void)
{
	_k_heap_init();
	_k_memproof_init();
}

//
void _k_mem_disp(void)
{
	_k_memproof_disp();
	_k_heap_disp();
}

/**
 * 메모리 블럭 크기 계산.
 * 메모리 블럭은 시스템에 따라 다르나 보통 16바이트 정렬 또는 
 * 256바이트 인접 메모리 크기로 만들어 주는 기능을 한다
 * @param	size	계산할 메모리 크기.
 * @return	계산된 메모리 크기.
 */
ksize_t k_memcalcblock(ksize_t size)
{
	return k_membsize(size);
}

/**
 * 메모리 제거.
 * k_alloc 함수 또는 k_allocf 함수로 할당한 메모리를 해제한다
 * @param	ptr	포인터.
 * @sa k_delete k_alloc k_allocf				
 */
void k_free(kpointer ptr)
{
#if SB_BUILD_ALLOC
	if (ptr)
	{
		kMemBlock* node = k_memhdr(ptr);
		k_mpffree(node);
	}
#else
	if (ptr)
		free(ptr);
#endif
}

/**
 * 메모리 할당. 메모리는 0으로 초기화 된다.
 * @param	size	할당할 크기.
 * @return	할당한 메모리 포인터.
 * @sa k_new k_new_1 k_new_0 k_allocf k_free
 */
kpointer k_alloc(ksize_t size)
{
#if SB_BUILD_ALLOC
	kMemBlock* node;
	kpointer ptr;

	k_return_value_if_fail(size != 0, NULL);

	node = k_mpfalloc(size);
	node->filename = NULL;
	node->line = 0;

	ptr = k_memptr(node);

#if _SB_DEBUG_
	k_memtest(ptr);
#endif

	return ptr;
#else
	return size == 0 ? NULL : calloc(size, sizeof(kbyte));
#endif
}

/**
 * 메모리 할당. 메모리를 초기화하지 않고 더미 데이터가 들어 있을 수 있다.
 * @param	size	할당할 크기.
 * @return	kpointer 할당한 메모리 포인터.
 * @sa k_new k_new_1 k_new_0 k_alloc k_free
 */
kpointer k_allocf(ksize_t size)
{
#if SB_BUILD_ALLOC
	kMemBlock* node;
	kpointer ptr;

	k_return_value_if_fail(size != 0, NULL);

	node = k_mpfallocf(size);
	node->filename = NULL;
	node->line = 0;

	ptr = k_memptr(node);

#if _SB_DEBUG_
	k_memtest(ptr);
#endif

	return ptr;
#else
	return size == 0 ? NULL : malloc(size);
#endif
}

/**
 * 메모리 재할당. k_alloc 함수 또는 k_allocf 함수로 할당한 메모리를
 * 재할당 한다. 다만 메모리는 초기화하지 않고 더미 데이터가 들어 있을 수 있다.
 * @param	ptr 	재할당할 포인터.
 * @param	size	할당할 크기.
 * @return	할당한 메모리 포인터.
 * @sa k_alloc k_allocf k_free
 */
kpointer k_realloc(kpointer ptr, ksize_t size)
{
#if SB_BUILD_ALLOC
	if (!ptr)
		return k_allocf(size);
	else
	{
		kMemBlock* node = k_memhdr(ptr);

		if (size == 0)
		{
			// 제거
			k_mpffree(node);
			return NULL;
		}
		else
		{
			ksize_t block = k_memcalcblock(size);

			if (block <= node->block)
			{
				// 블럭이 같으면 그냥 씀
				node->size = size;
				return ptr;
			}
			else
			{
				// 새블럭 만들고 복사
				kMemBlock* newnode = k_mpfallocf(size);
				newnode->filename = NULL;
				newnode->line = 0;

				if (node->size)
					memcpy(k_memptr(newnode), k_memptr(node), node->size);

				k_mpffree(node);

				return k_memptr(newnode);
			}
		}
	}
#else
	return realloc(ptr, size);
#endif
}

/**
 * 메모리 할당, 디버그용. 0으로 초기화 된다.
 * 이 함수로 메모리를 할당하면 할당 위치의 디버그 정보가 메모리에 포함된다.
 * @param	size		할당할 크기.
 * @param	filename	파일의 이름.
 * @param	line		파일의 줄 번호.
 * @return	할당한 메모리 포인터.
 */
kpointer k_alloc_dbg(ksize_t size, const char* filename, kint line)
{
#if SB_BUILD_ALLOC
	kMemBlock* node;
	kpointer ptr;

	k_return_value_if_fail(size != 0, NULL);

	node = k_mpfalloc(size);
	node->filename = filename;
	node->line = (kushort)line;

	ptr = k_memptr(node);

#if _SB_DEBUG_
	k_memtest(ptr);
#endif

	return ptr;
#else
	return size == 0 ? NULL : calloc(size, sizeof(kbyte));
#endif
}

/**
 * 메모리 할당, 디버그용. 메모리 내용은 초기화되지 않는다.
 * 이 함수로 메모리를 할당하면 할당 위치의 디버그 정보가 메모리에 포함된다.
 * @param	size		할당할 크기.
 * @param	filename	파일의 이름.
 * @param	line		파일의 줄 번호.
 * @return	할당한 메모리 포인터.
 */
kpointer k_allocf_dbg(ksize_t size, const char* filename, kint line)
{
#if SB_BUILD_ALLOC
	kMemBlock* node;
	kpointer ptr;

	k_return_value_if_fail(size != 0, NULL);

	node = k_mpfallocf(size);
	node->filename = filename;
	node->line = (kushort)line;

	ptr = k_memptr(node);

#if _SB_DEBUG_
	k_memtest(ptr);
#endif

	return ptr;
#else
	return size == 0 ? NULL : malloc(size);
#endif
}

/**
 * 메모리 재할당, 디버그. k_alloc... 으로 시작한 모든 메모리 포인터를
 * 재할당 할 수 있다.
 * 재할당 결과는 원래 포인터와 같을 수도 있다.
 * 이 함수로 메모리리를 할당하면 할당 위치의 디버그 정보가 메모리에 포함된다.
 * @param	ptr			재할당할 포인터.
 * @param	size		할당할 크기.
 * @param	filename	파일의 이름.
 * @param	line		파일의 줄 번호.
 * @return	할당한 메모리 포인터.
 */
kpointer k_realloc_dbg(kpointer ptr, ksize_t size, const char* filename, kint line)
{
#if SB_BUILD_ALLOC
	if (ptr == NULL)
		return k_allocf_dbg(size, filename, line);
	else
	{
		kMemBlock* node = k_memhdr(ptr);

		if (size == 0)
		{
			// 제거
			k_mpffree(node);
			return NULL;
		}
		else
		{
			ksize_t block = k_memcalcblock(size);

			if (block <= node->block)
			{
				// 블럭이 같으면 그냥 씀
				node->filename = filename;
				node->line = (kushort)line;
				node->size = size;
				return ptr;
			}
			else
			{
				// 새블럭 만들고 복사
				kMemBlock* newnode = k_mpfallocf(size);
				newnode->filename = filename;
				newnode->line = (kushort)line;

				if (node->size)
					memcpy(k_memptr(newnode), k_memptr(node), node->size);

				k_mpffree(node);

				return k_memptr(newnode);
			}
		}
	}
#else
	return realloc(ptr, size);
#endif
}

/**
 * 마스크 카피. 데이터를 마스크하여 복제한다
 * @param	dest	대상.
 * @param	src 	원본.
 * @param	size	원본 크기.
 * @param	mask	마스크 방식으로 0으로 인코딩 1로 디코딩할 수 있다.
 * @return	dest 인수를 반환한다.
 */
kpointer k_copymask(kpointer dest, kconstpointer src, ksize_t size, kint mask)
{
	kbyte* pd = (kbyte*)dest;
	kbyte* ps = (kbyte*)src;
	kbyte z;

	if (mask == 0)
	{
		for (; size; --size, ++pd, ++ps)
		{
			z = (kbyte)(255 - *ps);
			*pd = (kbyte)(z >> 4 | (z & 0x0F) << 4);
		}
	}
	else if (mask == 1)
	{
		for (; size; --size, ++pd, ++ps)
		{
			z = (kbyte)(255 - *ps);
			*pd = (kbyte)(z << 4 | (z & 0xF0) >> 4);
		}
	}

	return dest;
}

/**
 * 메모리 압축. zlib를 이용한다.
 * 반환받는 압축된 데이터는 사용하고 k_free 함수로 해제해야 한다.
 * @param	src					원본.
 * @param	srcsize				원본의 크기.
 * @param [반환]	destsize	(널값이 아니면) 압축된 크기.
 * @return	압축된 데이터.
 */
kpointer k_zcompress(kconstpointer src, ksize_t srcsize, ksize_t* destsize)
{
	kbyte* p;
	uLong tmp;

	k_return_value_if_fail(src != NULL, NULL);
	k_return_value_if_fail(srcsize != 0, NULL);

	tmp = ((uLong)srcsize + 12) / 1000;

	if (tmp < 1)
		tmp = (uLong)srcsize + 13;
	else
		tmp += (uLong)srcsize + 12;

	p = k_new(tmp, kbyte);

	if (compress((Bytef*)p, &tmp, (Bytef*)src, (uLong)srcsize) != Z_OK)
	{
		k_free(p);
		return NULL;
	}

	if (destsize)
		*destsize = tmp;

	return p;
}

/**
 * 메모리 압축 해제. zlib를 이용한다.
 * 반환 받은 압출 해제된 데이터는 사용하고 k_free 함수로 해제해야 한다
 * @param	src					원본.
 * @param	srcsize				원본의 크기.
 * @param	bufsize				해제할 버퍼의 크기. 이 값을 모르면 0으로 둘 수 있지만 기본적으로 원본 크기의 5배 정도를 사용한다.
 * @param [반환]	destsize	(널값이 아니면) 압축 해제한 크기.
 * @return	압축 해제한 데이터.
 */
kpointer k_zuncompress(kconstpointer src, ksize_t srcsize, ksize_t bufsize, ksize_t* destsize)
{
	kbyte* p;
	uLong size;

	k_return_value_if_fail(src != NULL, NULL);
	k_return_value_if_fail(srcsize != 0, NULL);

	size = (bufsize == 0) ? (uLong)srcsize * 5 + 12 : (uLong)bufsize;
	p = k_new(size, kbyte);

	if (uncompress((Bytef*)p, &size, (const Bytef*)src, (uLong)srcsize) != Z_OK)
	{
		k_free(p);
		return NULL;
	}

	if (destsize)
		*destsize = size;

	return p;
}

/**
 * 메모리 압축 해제.
 * 메모리 버퍼를 입력 받기 때문에 모든 인수 값이 정확하게 일치해야 한다.
 * @param	dst			대상.
 * @param	destsize	대상 크기.
 * @param	src			원본.
 * @param	srcsize 	원본 크기.
 * @return	성공하면 참, 실패하면 거짓.
 */
kcham k_zucmprsb(kpointer dst, ksize_t destsize, kconstpointer src, ksize_t srcsize)
{
	uLong size;

	k_return_value_if_fail(dst != NULL, FALSE);
	k_return_value_if_fail(destsize != 0, FALSE);
	k_return_value_if_fail(src != NULL, FALSE);
	k_return_value_if_fail(srcsize != 0, FALSE);

	size = (uLong)destsize;

	if (uncompress((Bytef*)dst, &size, (const Bytef*)src, (uLong)srcsize) != Z_OK)
		return FALSE;

	return TRUE;
}

/**
 * 메모리 정렬 검사. 16바이트 정렬인지 아닌지 검사하고 정렬되어 있지 않으면 정렬에 필요한
 * 메모리 크기를 알려준다.
 * @param	size	검사할 크기.
 * @return	정렬되어 있다면 0을 반환하고 아니면 정렬에 필요한 메모리 크기를 반환.
 */
ksize_t k_memaligned(ksize_t size)
{
	ksize_t align = size % 16;
	ksize_t advise;

	if (align == 0)
		return 0;
	else
	{
		advise = (size + KMEM_PARA_SIZE - 1) & ~(KMEM_PARA_SIZE - 1);
		return advise;
	}
}

/**
 * 메모리를 사람이 이해할 수 있는 단위로 계산하여 알려준다.
 * @param	size	   	메모리 크기.
 * @param [반환]	out	계산된 메모리 크기. 만약 널값이면 계산하지 않는다.
 * @return	해당하는 메모리 크기 규격을 알려준다 'k'는 킬로바이트, 'm'은 메가바이트, 'g'는 기가바이트를 의미한다.
 */
int k_memudch(ksize_t size, double* out)
{
	if (!out)
		return ' ';

	if (size > 1024 * 1024 * 1024)
	{
		*out = (double)size / (double)(1024 * 1024 * 1024);
		return 'g';
	}

	if (size > 1024 * 1024)
	{
		*out = (double)size / (double)(1024 * 1024);
		return 'm';
	}

	if (size > 1024)
	{
		*out = (double)size / (double)(1024);
		return 'k';
	}

	*out = (double)size;
	return ' ';
}

/**
 * 메모리를 사람이 알 수 있게 문자열로 만들어 준다.
 * 이 함수는 내부에서 k_memudch 함수를 호출하여 문자열 버퍼로 만들어 준다
 * @param [반환]	buf	(널값이 아니면) 문자열 버퍼.
 * @param	buflen	   	버퍼의 크기.
 * @param	size	   	만들어줄 크기.
 * @return	반환된 버퍼의 문자열 길이.
 * @sa k_memudch
 */
kint k_memuds(char* buf, kint buflen, ksize_t size)
{
	double out;
	kint usage = k_memudch(size, &out);
	return usage == ' ' ?
		k_snprintf(buf, buflen, "%Lu ", size) :
		k_snprintf(buf, buflen, "%.2f %c", out, usage);
}

/**
 * 메모리 테스트. 이 함수는 내부 메모리 관리자에서 할당한 메모리만 검사하며
 * 윈도우 환경에서만 동작한다 (WIN32/64 공통)
 * @param	ptr	테스트할 포인터.
 * @return	성공하면 참, 실패하면 거짓.
 */
kcham k_memtest(kpointer ptr)
{
#if SB_BUILD_ALLOC
	kMemBlock* node = k_memhdr(ptr);

	if (node && k_heapvalid(node) /*&& node->index != K_MAX_BYTE*/)	// index를 비교하면 안된다 2015-3-5
		return TRUE;
	else
		return FALSE;

#else
	return ptr != NULL;
#endif
}

/**
 * 메모리 덤프. 메모리를 읽을 수 있게 문자열로 덤프해 준다.
 * @param	ptr		   	덤프할 포인터.
 * @param	size	   	덤프할 메모리의 길이.
 * @param [반환]	buf	출력할 메모리 버퍼.
 * @param	len		   	메모리 버퍼의 길이.
 * @return	문제가 있거나 실패하면 널값을 반환, 성공할 때 반환값은 buf 포인터.
 */
char* k_memdmp(kconstpointer ptr, ksize_t size, char* buf, ksize_t len)
{
	const kbyte* mem = (const kbyte*)ptr;
	char* tmp = buf;
	ksize_t i, cnt;

	k_return_value_if_fail(ptr != NULL, NULL);
	k_return_value_if_fail(buf != NULL, NULL);

	if (size == 0 || len == 0)
	{
		*buf = '\0';
		return buf;
	}

	for (i = 0, cnt = 1; i < size && cnt < len; i++, cnt++)
	{
		kbyte ch = *mem;
		*tmp = (ch < 0x20 || ch > 0x7F) ? ' ' : (char)ch;
		mem++;
		tmp++;
	}

	*tmp = '\0';

	return buf;
}

/**
 * 메모리 덤프. 메모리를 읽을 수 있게 16진수로 덤프해 준다.
 * @param	ptr		   	덤프할 포인터.
 * @param	size	   	덤프할 메모리의 길이.
 * @param [반환]	buf	출력할 메모리 버퍼.
 * @param	len		   	메모리 버퍼의 길이.
 * @return	문제가 있거나 실패하면 널값을 반환, 성공할 때 반환값은 buf 포인터.
 */
char* k_memdmp_hex(kconstpointer ptr, ksize_t size, char* buf, ksize_t len)
{
	const kbyte* mem = (const kbyte*)ptr;
	char* tmp = buf;
	ksize_t i, cnt;

	k_return_value_if_fail(ptr != NULL, NULL);
	k_return_value_if_fail(buf != NULL, NULL);

	if (size == 0 || len == 0)
	{
		*buf = '\0';
		return buf;
	}

	for (i = 0, cnt = 4; i < size && cnt < len; i++, cnt += 3)
	{
		k_snprintf(tmp, 4, "%02X ", (int)*mem);
		mem++;
		tmp += 3;
	}

	*tmp = '\0';

	return buf;
}


//////////////////////////////////////////////////////////////////////////
// zlib 호환

/**
 * z_inflate_reset.
 * @param	strm	strm.
 * @return	int.
 */
KAPI int kz_inflate_reset(z_streamp strm)
{
	return inflateReset(strm);
}

/**
 * z_inflate.
 * @param	strm 	strm.
 * @param	flush	flush.
 * @return	int.
 */
KAPI int kz_inflate(z_streamp strm, int flush)
{
	return inflate(strm, flush);
}

/**
 * z_inflate_end.
 * @param	strm	strm.
 * @return	int.
 */
KAPI int kz_inflate_end(z_streamp strm)
{
	return inflateEnd(strm);
}

/**
 * z_inflate_init.
 * @param	strm	strm.
 * @return	int.
 */
KAPI int kz_inflate_init(z_streamp strm)
{
	return inflateInit(strm);
}

/**
 * z_crc32.
 * @param	crc	CRC.
 * @param	buf	버퍼.
 * @param	len	length.
 * @return	uLong.
 */
KAPI uLong kz_crc32(uLong crc, const Bytef *buf, uInt len)
{
	return crc32(crc, buf, len);
}

/**
 * z_deflate.
 * @param	strm 	strm.
 * @param	flush	flush.
 * @return	int.
 */
KAPI int kz_deflate(z_streamp strm, int flush)
{
	return deflate(strm, flush);
}

/**
 * z_deflateEnd.
 * @param	strm	strm.
 * @return	int.
 */
KAPI int kz_deflateEnd(z_streamp strm)
{
	return deflateEnd(strm);
}

/**
 * z_deflateReset.
 * @param	strm	strm.
 * @return	int.
 */
KAPI int kz_deflateReset(z_streamp strm)
{
	return deflateReset(strm);
}

/**
 * z_deflateInit2.
 * @param	strm	  	strm.
 * @param	level	  	level.
 * @param	method	  	method.
 * @param	windowBits	windowBits.
 * @param	memLevel  	memLevel.
 * @param	strategy  	strategy.
 * @return	int.
 */
KAPI int kz_deflateInit2(z_streamp strm, int level, int method, int windowBits, int memLevel, int strategy)
{
	return deflateInit2(strm, level, method, windowBits, memLevel, strategy);
}
